package com.gitee.mp.domain.condition;

import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.fastjson.JSON;
import com.gitee.mp.domain.definition.FieldDefinition;
import com.gitee.mp.infra.constants.LogicalOperator;
import com.gitee.mp.infra.constants.NormalOperator;
import com.gitee.mp.infra.utils.SerializableLambdaUtils;
import lombok.Getter;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hongda.li
 */
public class ConditionBuilder<Entity> {

    private final String expression;

    @Getter
    private final Class<Entity> entity;

    private final List<List<?>> vectorList = new ArrayList<>();

    private final List<String> outputFieldList = new ArrayList<>();

    private final List<Condition> conditionList = new ArrayList<>();

    private Long limit;

    private Long offset;

    private ConditionBuilder() {
        this(null, null);
    }

    private ConditionBuilder(Class<Entity> entity, String expression) {
        this.entity = entity;
        this.expression = expression;
    }

    public static <Entity> ConditionBuilder<Entity> newBuilder() {
        return newBuilder(null, null);
    }

    public static <Entity> ConditionBuilder<Entity> newBuilder(String expression) {
        return newBuilder(null, expression);
    }

    public static <Entity> ConditionBuilder<Entity> newBuilder(Class<Entity> entity) {
        return newBuilder(entity, null);
    }

    public static <Entity> ConditionBuilder<Entity> newBuilder(Class<Entity> entity, String expression) {
        return new ConditionBuilder<>(entity, expression);
    }

    public <Property> ConditionBuilder<Entity> select(Func1<Entity, Property> lambda) {
        this.outputFieldList.add(SerializableLambdaUtils.parseFieldName(lambda));
        return this;
    }

    public <Property> ConditionBuilder<Entity> andEq(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, this.wrapIfNecessary(value), NormalOperator.EQ);
    }

    public <Property> ConditionBuilder<Entity> andNeq(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, this.wrapIfNecessary(value), NormalOperator.NEQ);
    }

    public <Property extends Number> ConditionBuilder<Entity> andGt(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, value, NormalOperator.GT);
    }

    public <Property extends Number> ConditionBuilder<Entity> andGte(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, value, NormalOperator.GTE);
    }

    public <Property extends Number> ConditionBuilder<Entity> andLt(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, value, NormalOperator.LT);
    }

    public <Property extends Number> ConditionBuilder<Entity> andLte(Func1<Entity, Property> lambda, Property value) {
        return this.andBinaryOperator(lambda, value, NormalOperator.LTE);
    }

    public <Property> ConditionBuilder<Entity> andIn(Func1<Entity, Property> lambda, Collection<Property> value) {
        return this.andBinaryOperator(lambda, JSON.toJSONString(value), NormalOperator.IN);
    }

    public <Property> ConditionBuilder<Entity> andNotIn(Func1<Entity, Property> lambda, Collection<Property> value) {
        return this.andBinaryOperator(lambda, JSON.toJSONString(value), NormalOperator.NOT_IN);
    }

    public ConditionBuilder<Entity> andStartWith(Func1<Entity, String> lambda, String value) {
        return this.andBinaryOperator(lambda, value + "%", NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> andEndWith(Func1<Entity, String> lambda, String value) {
        return this.andBinaryOperator(lambda, "%" + value, NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> andContains(Func1<Entity, String> lambda, String value) {
        return this.andBinaryOperator(lambda, "%" + value + "%", NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> andIsEmpty(Func1<Entity, String> lambda) {
        return this.andBinaryOperator(lambda, "\"\"", NormalOperator.EQ);
    }

    public ConditionBuilder<Entity> andIsNotEmpty(Func1<Entity, String> lambda) {
        return this.andBinaryOperator(lambda, "\"\"", NormalOperator.NEQ);
    }

    public <Property> ConditionBuilder<Entity> orEq(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, this.wrapIfNecessary(value), NormalOperator.EQ);
    }

    public <Property> ConditionBuilder<Entity> orNeq(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, this.wrapIfNecessary(value), NormalOperator.NEQ);
    }

    public <Property extends Number> ConditionBuilder<Entity> orGt(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, value, NormalOperator.GT);
    }

    public <Property extends Number> ConditionBuilder<Entity> orGte(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, value, NormalOperator.GTE);
    }

    public <Property extends Number> ConditionBuilder<Entity> orLt(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, value, NormalOperator.LT);
    }

    public <Property extends Number> ConditionBuilder<Entity> orLte(Func1<Entity, Property> lambda, Property value) {
        return this.orBinaryOperator(lambda, value, NormalOperator.LTE);
    }

    public <Property> ConditionBuilder<Entity> orIn(Func1<Entity, Property> lambda, Collection<Property> value) {
        return this.orBinaryOperator(lambda, JSON.toJSONString(value), NormalOperator.IN);
    }

    public <Property> ConditionBuilder<Entity> orNotIn(Func1<Entity, Property> lambda, Collection<Property> value) {
        return this.orBinaryOperator(lambda, JSON.toJSONString(value), NormalOperator.NOT_IN);
    }

    public ConditionBuilder<Entity> orStartWith(Func1<Entity, String> lambda, String value) {
        return this.orBinaryOperator(lambda, value + "%", NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> orEndWith(Func1<Entity, String> lambda, String value) {
        return this.orBinaryOperator(lambda, "%" + value, NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> orContains(Func1<Entity, String> lambda, String value) {
        return this.orBinaryOperator(lambda, "%" + value + "%", NormalOperator.LIKE);
    }

    public ConditionBuilder<Entity> orIsEmpty(Func1<Entity, String> lambda) {
        return this.orBinaryOperator(lambda, "\"\"", NormalOperator.EQ);
    }

    public ConditionBuilder<Entity> orIsNotEmpty(Func1<Entity, String> lambda) {
        return this.orBinaryOperator(lambda, "\"\"", NormalOperator.NEQ);
    }

    public ConditionBuilder<Entity> search(List<?> vectorList) {
        this.vectorList.add(vectorList);
        return this;
    }

    private ConditionBuilder<Entity> andBinaryOperator(Func1<?, ?> lambda,
                                                       Object value,
                                                       NormalOperator operator) {
        return this.binaryOperator(lambda, value, operator, LogicalOperator.AND);
    }

    private ConditionBuilder<Entity> orBinaryOperator(Func1<?, ?> lambda,
                                                      Object value,
                                                      NormalOperator operator) {
        return this.binaryOperator(lambda, value, operator, LogicalOperator.OR);
    }

    private ConditionBuilder<Entity> binaryOperator(Func1<?, ?> lambda,
                                                    Object value,
                                                    NormalOperator normalOperator,
                                                    LogicalOperator logicalOperator) {
        if (value == null) {
            return this;
        }
        conditionList.add(new Condition(SerializableLambdaUtils.parseFieldName(lambda),
                normalOperator.getName(),
                value,
                logicalOperator));
        return this;
    }

    public ConditionBuilder<Entity> pageInfo(PageInfo pageInfo) {
        this.limit = pageInfo.getSize();
        this.offset = pageInfo.getPage() * pageInfo.getSize();
        return this;
    }

    public ConditionBuilder<Entity> limit(long limit) {
        this.limit = limit;
        return this;
    }

    public ConditionBuilder<Entity> offset(long offset) {
        this.offset = offset;
        return this;
    }

    private Object wrapIfNecessary(Object value) {
        if (value instanceof CharSequence cs) {
            return CharSequenceUtil.wrap(cs, "\"", "\"");
        } else {
            return value;
        }
    }

    public ConditionResult build(List<FieldDefinition> definitionList) {
        ConditionResult result = new ConditionResult();

        // 数据量与偏移量
        result.setLimit(this.limit);
        result.setOffset(this.offset);

        // 检索向量
        result.setVectorList(this.vectorList);

        // 实体字段 -> 集合字段映射关系
        Map<String, String> mapping = definitionList.stream().collect(Collectors.toMap(FieldDefinition::getFrom, FieldDefinition::getFieldName));

        // 替换查询字段
        if (CollectionUtils.isEmpty(this.outputFieldList)) {
            this.outputFieldList.addAll(definitionList.stream()
                    .map(FieldDefinition::getFieldName)
                    .toList());
        } else {
            this.outputFieldList.replaceAll(mapping::get);
        }
        result.setOutputFields(this.outputFieldList);

        // 自定义查询条件
        if (this.expression != null) {
            result.setExpression(this.expression);
            return result;
        }

        // 替换查询条件
        if (!CollectionUtils.isEmpty(this.conditionList)) {
            StringBuilder appender = new StringBuilder();

            Iterator<Condition> iterator = this.conditionList.iterator();
            Condition first = iterator.next();
            appender.append(first.combine(mapping::get));

            while (iterator.hasNext()) {
                Condition next = iterator.next();
                appender.append(CharSequenceUtil.SPACE)
                        .append(next.linkOperator().name().toLowerCase(Locale.ROOT))
                        .append(CharSequenceUtil.SPACE)
                        .append(next.combine(mapping::get));
            }

            result.setExpression(appender.toString());
        }

        return result;
    }
}
